#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdint.h>
#if defined(_WIN32) || defined(_WIN64)
#include <iphlpapi.h>
#include <tlhelp32.h>  
#include <psapi.h>
#else
#include <arpa/inet.h>
#include <ifaddrs.h>  
#endif
#include <getopt.h>
#include "broadcast.h"

int show(struct broadcast_remote_info *info, void *arg) {
    printf("get remote ip %s, name %s\n", info->remote_ip, info->name);
    return 0;
}

void usage() {
    printf("Usage: \n");
    printf("  for server:  broadcast \n");
    printf("  for client:  broadcast -t -1 --keep-alive\n");
    printf("Options:\n");
    printf("  -h, --help       Display this help message \n");
    printf("  --ip,            Bind locol ip \n");
    printf("  -p, --port       Select broadcast port \n");
    printf("  -m, --mode       Select broadcast mode, MOSI(default) or MISO \n");
    printf("  -t, --timeout    Each broadcast continue time (second) \n");
    printf("  -s, --server     Broadcast will work as server(default) \n");
    printf("  -c, --client     Broadcast will work as client \n");
    printf("  --keep-alive     Broadcast will keep alive if mode is input \n");
    printf("  -v, --version    Display version \n");
    printf("  --code           Setting pairing code to match similar one \n");
    printf("  --name           Set device name \n");
    printf("  --en-verify      Testing only \n");
}

/* param */
int code = 0;
int en_verify = 0;
char name[48] = {0};

bool verify_sample(char *data, void *arg) {
    // data is from remote device data
    if (data) printf("verify data is %s \n", data);
    else {
        printf("verify data is empty \n");
        return false;
    }
    return true;
}

int server_do_once(broadcast_mode mode, char* ip, uint16_t port, int timeout, int is_keep) {
    struct broadcast_handle hd;
    if (broadcast_server_init2(&hd, mode, ip, port, timeout)) {
        printf("broadcast initialize fail: %s\n", broadcast_getErr(&hd));
        return 1;
    }
    if (code) set_pairing_code(&hd, code);
    if (en_verify) set_user_verify(&hd, "VBPNQdM6bZ9q7iU7OnwnTikxUwOWNg+ETDoANl0T6wtn7VygekQ7FuLrQJBbk716", 64, verify_sample, NULL);
    if (strlen(name)) set_device_name(&hd, name); 
    do {
        if (broadcast_server_run(&hd)) {
            printf("broadcast run fail: %s\n", broadcast_getErr(&hd));
            broadcast_close(&hd);
            return 0;
        }
        broadcast_get_remote_info(&hd, show, NULL);
    } while (is_keep && (mode==B_MODE_MISO));
    broadcast_close(&hd);
    return 0;
}

int client_do_once(broadcast_mode mode, char* ip, uint16_t port, int timeout, int is_keep) {
    struct broadcast_handle hd;
    if (broadcast_client_init2(&hd, mode, ip, port, timeout)) {
        printf("broadcast initialize fail: %s\n", broadcast_getErr(&hd));
        return 1;
    }
    if (code) set_pairing_code(&hd, code);
    if (en_verify) set_user_verify(&hd, "VBPNQdM6bZ9q7iU7OnwnTikxUwOWNg+ETDoANl0T6wtn7VygekQ7FuLrQJBbk716", 64, verify_sample, NULL);
    if (strlen(name)) set_device_name(&hd, name); 
    do {
        if (broadcast_client_run(&hd)) {
            printf("broadcast run fail: %s\n", broadcast_getErr(&hd));
            broadcast_close(&hd);
            return 0;
        }
        broadcast_get_remote_info(&hd, show, NULL);
    } while (is_keep && (mode==B_MODE_MOSI));
    broadcast_close(&hd);
    return 0;
}

#if defined(_WIN32) || defined(_WIN64)
DWORD GetParentProcessId(DWORD dwProcessId) {  
    #if 0
    HANDLE hSnapshot;  
    PROCESSENTRY32 pe32;  
    DWORD dwParentProcessId = 0;  
  
    // Take a snapshot of all processes in the system.  
    hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);  
    if (hSnapshot == INVALID_HANDLE_VALUE) {  
        printf("CreateToolhelp32Snapshot (of processes) failed. (%d)\n", GetLastError());  
        return 0;  
    }  
  
    pe32.dwSize = sizeof(PROCESSENTRY32);  
  
    // Walk the snapshot to retrieve the processes in the system.  
    if (Process32First(hSnapshot, &pe32)) {  
        do {  
            if (pe32.th32ProcessID == dwProcessId) {  
                // Found the process, retrieve its parent process ID.  
                HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | SYNCHRONIZE, FALSE, pe32.th32ParentProcessID);  
                if (hProcess != NULL) {  
                    dwParentProcessId = pe32.th32ParentProcessID;  
                    CloseHandle(hProcess);  
                    break;  
                }  
            }  
        } while (Process32Next(hSnapshot, &pe32));  
    }  
  
    CloseHandle(hSnapshot);  
  
    return dwParentProcessId;  
    #else
    HANDLE hSnapshot;    
    PROCESSENTRY32 pe32;    
    DWORD dwParentProcessId = 0;    
  
    // 创建一个系统所有进程的快照  
    hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);    
    if (hSnapshot == INVALID_HANDLE_VALUE) {    
        printf("CreateToolhelp32Snapshot (of processes) failed. (%ld)\n", GetLastError());    
        return 0;    
    }    
  
    pe32.dwSize = sizeof(PROCESSENTRY32);    
  
    // 遍历快照以检索系统中的进程  
    if (Process32First(hSnapshot, &pe32)) {    
        do {    
            if (pe32.th32ProcessID == dwProcessId) {    
                // 找到进程，直接获取其父进程ID  
                dwParentProcessId = pe32.th32ParentProcessID;    
                break;    
            }    
        } while (Process32Next(hSnapshot, &pe32));    
    }    
  
    CloseHandle(hSnapshot);    
  
    return dwParentProcessId; 
    #endif
}
#endif

int IsRunByDoubleClick() {
    #ifdef __linux__   
    return 0;
    #elif defined(_WIN32) || defined(_WIN64)
    extern DWORD GetParentProcessId(DWORD dwProcessId);
    DWORD dwCurrentProcessId = GetCurrentProcessId();
    DWORD dwParentProcessId = GetParentProcessId(dwCurrentProcessId);  

    if (dwParentProcessId != 0) {  
        // printf("Parent process ID is %ld\n", dwParentProcessId);
        HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, dwParentProcessId);  
        if (hProcess != NULL) {  
            DWORD dwSize = MAX_PATH;  
            TCHAR parentProcessPath[MAX_PATH];  
            if (QueryFullProcessImageName(hProcess, 0, parentProcessPath, &dwSize)) {
                #ifdef BROADCAST_DEBUG
                printf("Parent process path: %s\n", parentProcessPath);
                #endif
  
                // 检查父进程名称（仅作为示例）  
                if (strstr(parentProcessPath, TEXT("explorer.exe")) != NULL) {
                    #ifdef BROADCAST_DEBUG
                    printf("The program might have been run by double-clicking an icon.\n");
                    #endif
                    CloseHandle(hProcess);
                    return 1;
                } else {  
                    #ifdef BROADCAST_DEBUG
                    printf("The program might not have been run by double-clicking an icon.\n");
                    #endif
                }  
            } else {  
                DWORD dwError = GetLastError();  
                printf("QueryFullProcessImageName failed (%ld)\n", dwError);  
            }  
            CloseHandle(hProcess);  
        } else {  
            DWORD dwError = GetLastError();  
            printf("OpenProcess failed (%ld)\n", dwError);  
        }  
    } else {
        #ifdef BROADCAST_DEBUG
        printf("Failed to retrieve parent process ID\n");
        #endif
    }
    return 0;
    #elif defined(__APPLE__) && defined(__MACH__) 
    // not support right now
    return 0;
    #else
    return 0;
    #endif
}

int main(int argc, char** argv) {
    uint16_t port = 4090;
    char ip[16] = {0};
    broadcast_mode mode = B_MODE_MOSI;
    int timeout = 10, keepAliveFlag = 0;
    int role = 1;   // server

    int opt, lopt;
    // 定义短选项字符串和长选项结构数组
    const char *short_options = "hscap:m:t:v";
    const struct option long_options[] = {
        {"help", no_argument, NULL, 'h'},
        {"server", no_argument, NULL, 's'},
        {"client", no_argument, NULL, 'c'},
        {"all", no_argument, NULL, 'a'},
        {"port", required_argument, NULL, 'p'},
        {"mode", required_argument, NULL, 'm'},
        {"timeout", required_argument, NULL, 't'},
        {"ip", required_argument, &lopt, 1},
        {"keep-alive", no_argument, &lopt, 2},
        {"version", no_argument, NULL, 'v'},
        {"code", required_argument, &lopt, 3},
        {"en-verify", no_argument, &lopt, 4},
        {"name", required_argument, &lopt, 5},
        {NULL, 0, NULL, 0}  // 结束标志
    };

    // 解析命令行参数
    while ((opt = getopt_long(argc, argv, short_options, long_options, NULL)) != -1) {
        switch (opt) {
            case 0:
            {
                switch (lopt)
                {
                case 1:
                    strncpy(ip, optarg, 15);
                    break;
                case 2:
                    keepAliveFlag = 1;
                    break;
                case 3:
                    code = atoi(optarg);
                    break;
                case 4:
                    en_verify = 1;
                    break;
                case 5:
                    strncpy(name, optarg, 47);
                    name[47] = 0;
                    break;
                default:
                    usage();
                    return 1;
                }
            }
            break;
            case 'h':
                usage();
                return 0;
            case 'p':
                port = (uint16_t)atoi(optarg);
                break;
            case 'm':
                if (strcmp("MOSI", optarg) == 0 || strcmp("mosi", optarg) == 0) mode = B_MODE_MOSI;
                else if (strcmp("MISO", optarg) == 0 || strcmp("miso", optarg) == 0) mode = B_MODE_MISO;
                else {usage(); return 1;}
                break;
            case 't':
                timeout = atoi(optarg);
                break;
            case 's':
                role = 1;
                break;
            case 'c':
                role = 0;
                break;
            case 'v':
                printf("%s %s\n", broadcast_version(), COMPLATE_MESSAGE);
                return 0;
            default:
                usage();
                return 1;
        }
    }
    
    setbuf(stdout, NULL);
    printf("Starting brocast as %s with mode %s\n", role?"server":"client", mode==B_MODE_MOSI?"MOSI":"MISO");
    printf("ip: %s, port %d\n", ip, port);
    broadcast_get("192.168.1.1", "255.255.212.0");
    int len;
    len = strlen(ip);
    if (len || (role&&(mode==B_MODE_MOSI)) || (!role&&(mode==B_MODE_MISO))) {
        #if defined(_WIN32) || defined(_WIN64) 
        PIP_ADAPTER_INFO pIpAdapterInfo = (PIP_ADAPTER_INFO)malloc(sizeof(IP_ADAPTER_INFO));
        unsigned long stSize = sizeof(IP_ADAPTER_INFO);
        int nRel = 0;
        nRel = GetAdaptersInfo(pIpAdapterInfo, &stSize);
        if (ERROR_BUFFER_OVERFLOW == nRel) {
            free(pIpAdapterInfo);
            pIpAdapterInfo = (PIP_ADAPTER_INFO)malloc(stSize);
            nRel = GetAdaptersInfo(pIpAdapterInfo, &stSize);
        }
        if (ERROR_SUCCESS == nRel) {
            while (pIpAdapterInfo) {
                // printf("name: %s\n",pIpAdapterInfo->Description);
                // printf("ip: %s\n", pIpAdapterInfo->IpAddressList.IpAddress.String);
                // printf("gw: %s\n", pIpAdapterInfo->GatewayList.IpAddress.String);
                // printf("mask: %s\n", pIpAdapterInfo->IpAddressList.IpMask.String);
                if (strcmp(pIpAdapterInfo->IpAddressList.IpAddress.String, "0.0.0.0") == 0) {
                    pIpAdapterInfo = pIpAdapterInfo->Next;
                    continue;
                }
                if (len && strcmp(pIpAdapterInfo->IpAddressList.IpAddress.String, ip) != 0) {
                    pIpAdapterInfo = pIpAdapterInfo->Next;
                    continue;
                } 
                // if ((role&&(mode==B_MODE_MOSI)) || (!role&&(mode==B_MODE_MISO)))    // windows can not bind broadcast address???
                    // strncpy(ip, broadcast_get(pIpAdapterInfo->IpAddressList.IpAddress.String,pIpAdapterInfo->IpAddressList.IpMask.String),15);
                if (!len) strncpy(ip, pIpAdapterInfo->IpAddressList.IpAddress.String, 15);
                // printf("broadcast: %s\n", ip);
                printf("\nSearching on apdpter: %s\n", pIpAdapterInfo->Description);
                if (role && server_do_once(mode, ip, port, timeout, keepAliveFlag)) {
                    free(pIpAdapterInfo);
                    return 1;
                }
                if (!role && client_do_once(mode, ip, port, timeout, keepAliveFlag)) {
                    free(pIpAdapterInfo);
                    return 1;
                }

                if (len) break;
                pIpAdapterInfo = pIpAdapterInfo->Next;
            }
        }
        if (pIpAdapterInfo) {
            free(pIpAdapterInfo);
        }
        #else   /* defined(_WIN32) || defined(_WIN64)  */
        struct sockaddr_in *sin = NULL;
        struct ifaddrs *ifa = NULL, *ifList;

        if (getifaddrs(&ifList) < 0) {
            printf("error line: %d\n", __LINE__);
            return -1;
        }

        for (ifa = ifList; ifa != NULL; ifa = ifa->ifa_next) {
            if (ifa->ifa_addr == NULL) continue;
            if(ifa->ifa_addr->sa_family == AF_INET) {    // have ipv4
                sin = (struct sockaddr_in *)ifa->ifa_addr;
                if (len && strcmp(inet_ntoa(sin->sin_addr), ip) != 0) {
                    continue;
                }
                // sin = (struct sockaddr_in *)ifa->ifa_dstaddr;
                // strncpy(ip, inet_ntoa(sin->sin_addr), 15);  // linux need to bind broadcast address
                // printf("broadcast: %s\n", ip);
                if (!len) strncpy(ip, inet_ntoa(sin->sin_addr), 15);
                printf("\nSearching on apdpter: %s\n", ifa->ifa_name);
                if (role && server_do_once(mode, ip, port, timeout, keepAliveFlag)) {
                    freeifaddrs(ifList);
                    return 1;
                }
                if (!role && client_do_once(mode, ip, port, timeout, keepAliveFlag)) {
                    freeifaddrs(ifList);
                    return 1;
                }
                if (len) break;
            }
        }
        freeifaddrs(ifList);
        #endif      /* defined(_WIN32) || defined(_WIN64)  */
    }
    else {
        if (role) {
            server_do_once(mode, NULL, port, timeout, keepAliveFlag);
        } else {
            client_do_once(mode, NULL, port, timeout, keepAliveFlag);
        }
    }

    if (IsRunByDoubleClick()) system("pause");

    return 0;
}




